<!DOCTYPE html>
<title>Subresource loading with link rel="webbundle"</title>
<link rel="help" href="https://github.com/WICG/webpackage/blob/master/explainers/subresource-loading.md" />
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>

<body>
  <link id="link-web-bundle" rel="webbundle" href="../resources/wbn/subresource.wbn" resources="https://web-platform.test:8444/web-bundle/resources/wbn/root.js
                   https://web-platform.test:8444/web-bundle/resources/wbn/submodule.js" />
  <script>
    promise_test(async () => {
      const module = await import('https://web-platform.test:8444/web-bundle/resources/wbn/root.js');
      assert_equals(module.result, 'OK');
    }, "Subresource loading with WebBundle");

    promise_test(async () => {
      const response = await fetch('https://web-platform.test:8444/web-bundle/resources/wbn/root.js');
      const text = await response.text();
      assert_equals(text, "export * from './submodule.js';\n");
    }, "Subresource loading with WebBundle (Fetch API)");

    promise_test(t => {
      const url =
        '/common/redirect.py?location=https://web-platform.test:8444/web-bundle/resources/wbn/root.js';
      return promise_rejects_js(t, TypeError, import(url));
    }, "Subresource loading with WebBundle shouldn't affect redirect");

    promise_test(async () => {
      const link = document.createElement("link");
      link.rel = "webbundle";
      link.href = "../resources/wbn/dynamic1.wbn";
      link.resources.add('https://web-platform.test:8444/web-bundle/resources/wbn/dynamic/resource1.js',
        'https://web-platform.test:8444/web-bundle/resources/wbn/dynamic/resource2.js',
        'https://web-platform.test:8444/web-bundle/resources/wbn/dynamic/resource4.js');
      document.body.appendChild(link);

      const module = await import('https://web-platform.test:8444/web-bundle/resources/wbn/dynamic/resource1.js');
      assert_equals(module.result, 'resource1 from dynamic1.wbn');

      link.href = "../resources/wbn/dynamic2.wbn";
      const module2 = await import('https://web-platform.test:8444/web-bundle/resources/wbn/dynamic/resource2.js');
      assert_equals(module2.result, 'resource2 from dynamic2.wbn');

      // A resource not specified in the resources attribute, but in the bundle.
      const module3 = await import('https://web-platform.test:8444/web-bundle/resources/wbn/dynamic/resource3.js');
      assert_equals(module3.result, 'resource3 from network');

      document.body.removeChild(link);
      const module4 = await import('https://web-platform.test:8444/web-bundle/resources/wbn/dynamic/resource4.js');
      assert_equals(module4.result, 'resource4 from network');

      // Module scripts are stored to the Document's module map once loaded.
      // So import()ing the same module script will reuse the previously loaded
      // script.
      const module_second = await import('https://web-platform.test:8444/web-bundle/resources/wbn/dynamic/resource1.js');
      assert_equals(module_second.result, 'resource1 from dynamic1.wbn');
    }, 'Dynamically adding / updating / removing "<link rel=webbundle>"');

    promise_test(async () => {
      const classic_script_url = 'https://web-platform.test:8444/web-bundle/resources/wbn/dynamic/classic_script.js';
      const link = document.createElement("link");
      link.rel = "webbundle";
      link.href = "../resources/wbn/dynamic1.wbn";
      link.resources.add(classic_script_url);
      document.body.appendChild(link);
      assert_equals(
        await loadScriptAndWaitReport(classic_script_url),
        'classic script from dynamic1.wbn');
      link.href = "../resources/wbn/dynamic2.wbn";
      // Loading the classic script should not reuse the previously loaded
      // script. So in this case, the script must be loaded from dynamic2.wbn.
      assert_equals(
        await loadScriptAndWaitReport(classic_script_url),
        'classic script from dynamic2.wbn');
      document.body.removeChild(link);
      // And in this case, the script must be loaded from network.
      assert_equals(
        await loadScriptAndWaitReport(classic_script_url),
        'classic script from network');
    }, 'Dynamically loading classic script from web bundle with link.resources');

    promise_test(async () => {
      const classic_script_url = 'https://web-platform.test:8444/web-bundle/resources/wbn/dynamic/classic_script.js';
      const scope = 'https://web-platform.test:8444/web-bundle/resources/wbn/dynamic/';
      const link = document.createElement("link");
      link.rel = "webbundle";
      link.href = "../resources/wbn/dynamic1.wbn";
      link.scopes.add(scope);
      document.body.appendChild(link);
      assert_equals(
        await loadScriptAndWaitReport(classic_script_url),
        'classic script from dynamic1.wbn');
      link.href = "../resources/wbn/dynamic2.wbn";
      // Loading the classic script should not reuse the previously loaded
      // script. So in this case, the script must be loaded from dynamic2.wbn.
      assert_equals(
        await loadScriptAndWaitReport(classic_script_url),
        'classic script from dynamic2.wbn');
      // Changes the scope not to hit the classic_script.js.
      link.scopes = scope + 'dummy';
      // And in this case, the script must be loaded from network.
      assert_equals(
        await loadScriptAndWaitReport(classic_script_url),
        'classic script from network');
      // Adds the scope to hit the classic_script.js.
      link.scopes.add(scope + 'classic_');
      assert_equals(
        await loadScriptAndWaitReport(classic_script_url),
        'classic script from dynamic2.wbn');
      document.body.removeChild(link);
      // And in this case, the script must be loaded from network.
      assert_equals(
        await loadScriptAndWaitReport(classic_script_url),
        'classic script from network');
    }, 'Dynamically loading classic script from web bundle with link.scopes');

    promise_test(() => {
      return addLinkAndWaitForLoad("../resources/wbn/dynamic1.wbn?test-event");
    }, '<link rel="webbundle"> fires a load event on load success');

    promise_test((t) => {
      return addLinkAndWaitForError("../resources/wbn/nonexistent.wbn");
    }, '<link rel="webbundle"> fires an error event on load failure');

    promise_test(async () => {
      const link = document.createElement('link');
      link.rel = 'webbundle';
      link.href = '../resources/wbn/dynamic1-crossorigin.wbn';
      link.resources = 'https://www1.web-platform.test:8444/web-bundle/resources/wbn/dynamic/resource1.js';
      document.body.appendChild(link);
      const module = await import(link.resources);
      assert_equals(module.result, 'resource1 from network');
    }, 'Subresource URL must be same-origin with bundle URL');

    promise_test(async () => {
      const url = 'urn:uuid:020111b3-437a-4c5c-ae07-adb6bbffb720';
      const link = document.createElement('link');
      link.rel = 'webbundle';
      link.href = '../resources/wbn/urn-uuid.wbn';
      link.resources = url;
      document.body.appendChild(link);
      assert_equals(await loadScriptAndWaitReport(url), 'OK');
      document.body.removeChild(link);
    }, 'Subresource loading with urn:uuid: URL with link.resources');

    promise_test(async () => {
      const url = 'urn:uuid:020111b3-437a-4c5c-ae07-adb6bbffb720';
      const link = document.createElement('link');
      link.rel = 'webbundle';
      link.href = '../resources/wbn/urn-uuid.wbn';
      link.scopes = 'urn:uuid:';
      document.body.appendChild(link);
      assert_equals(await loadScriptAndWaitReport(url), 'OK');
      document.body.removeChild(link);
    }, 'Subresource loading with urn:uuid: URL with link.scopes');

    promise_test(async () => {
      const wbn_url = 'https://web-platform.test:8444/web-bundle/resources/wbn/subresource.wbn?test-resources-update';
      const resource_url = 'https://web-platform.test:8444/web-bundle/resources/wbn/submodule.js';
      const link = await addLinkAndWaitForLoad(wbn_url);
      link.resources.add(resource_url);
      const resp = await fetch(resource_url, { cache: 'no-store' });
      assert_true(resp.ok);
      assert_equals(performance.getEntriesByName(wbn_url).length, 1);
    }, 'Updating resource= attribute should not reload the bundle');

    function addLinkAndWaitForLoad(url) {
      return new Promise((resolve, reject) => {
        const link = document.createElement("link");
        link.rel = "webbundle";
        link.href = url;
        link.onload = () => resolve(link);
        link.onerror = reject;
        document.body.appendChild(link);
      });
    }

    function addLinkAndWaitForError(url) {
      return new Promise((resolve, reject) => {
        const link = document.createElement("link");
        link.rel = "webbundle";
        link.href = url;
        link.onload = reject;
        link.onerror = () => resolve(link);
        document.body.appendChild(link);
      });
    }

    async function loadScriptAndWaitReport(script_url) {
      const result_promise = new Promise((resolve) => {
        // This function will be called from script.js
        window.report_result = resolve;
      });

      const script = document.createElement('script');
      script.src = script_url;
      document.body.appendChild(script);
      return result_promise;
    }
  </script>
</body>